from collections import OrderedDict
from django import views
from django.shortcuts import render, redirect, HttpResponse
from django.urls import reverse
from django.forms import formset_factory
from rbac import models
from rbac.forms.menu import MenuModelForm, SecMenuModelForm, PerMenuModelForm, MultiAddPermissionForm,\
    MultiEditPermissionForm
from rbac.permissions.get_url import get_all_url_dict


class Show(views.View):
    def get(self, request):
        menu_list = models.Menu.objects.all()
        menu_id = request.GET.get('mid')
        sec_menu_id = request.GET.get('sid')
        chi_per_list = None
        if sec_menu_id:
            sec_menu_id = int(sec_menu_id)
            chi_per_list = models.Permission.objects.filter(p_id=sec_menu_id)

        per_list = None
        if menu_id:
            per_list = models.Permission.objects.filter(menu_id=menu_id)
        if menu_id:
            menu_id = int(menu_id)

        return render(request, 'rbac/menu.html', {'menu_list': menu_list, 'menu_id': menu_id, 'per_list': per_list,
                                                  'sec_menu_id': sec_menu_id, 'chi_per_list': chi_per_list
                                                  })


class Add(views.View):
    def get(self, request):
        menu_form = MenuModelForm()
        return render(request, 'rbac/change.html', {'my_form': menu_form})

    def post(self, request):
        menu_form = MenuModelForm(data=request.POST)
        if menu_form.is_valid():
            menu_form.save()
            re_url = reverse('rbac_menu_list')
            params = request.GET.get('_addi')
            if params:
                re_url = '%s?%s' % (re_url, params)
            return redirect(re_url)
        else:
            return render(request, 'rbac/change.html', {'my_form': menu_form})


class Edit(views.View):
    def get(self, request, mid):
        obj = models.Menu.objects.filter(id=mid).first()
        if not obj:
            return HttpResponse('404 not found')

        menu_form = MenuModelForm(instance=obj)
        return render(request, 'rbac/change.html', {'my_form': menu_form})

    def post(self, request, mid):
        obj = models.Menu.objects.filter(id=mid).first()
        menu_form = MenuModelForm(instance=obj, data=request.POST)
        if menu_form.is_valid():
            menu_form.save()
            re_url = reverse('rbac_menu_list')
            params = request.GET.get('_addi')
            if params:
                re_url = '%s?%s' % (re_url, params)
            return redirect(re_url)
        else:
            return render(request, 'rbac/change.html', {'my_form': menu_form})


class Del(views.View):
    def get(self, request, mid):
        params = request.GET.get('_addi')
        re_url = reverse('rbac_menu_list')
        if params:
            re_url = '%s?%s' % (re_url, params)

        return render(request, 'rbac/delete.html', {'cancel': re_url})

    def post(self, request, mid):
        models.Menu.objects.filter(id=mid).delete()
        params = request.GET.get('_addi')
        re_url = reverse('rbac_menu_list')
        if params:
            re_url = '%s?%s' % (re_url, params)
        return redirect(re_url)


class AddSec(views.View):
    def get(self, request, menu_id):
        """
        :param request:
        :param menu_id: 已选择的一级菜单id
        :return:
        """

        menu_form = SecMenuModelForm(initial={'menu': menu_id})
        return render(request, 'rbac/change.html', {'my_form': menu_form})

    def post(self, request, menu_id):
        menu_form = SecMenuModelForm(data=request.POST)
        if menu_form.is_valid():
            menu_form.save()
            re_url = reverse('rbac_menu_list',)
            params = request.GET.get('_addi')
            if params:
                re_url = '%s?%s' % (re_url, params)
            return redirect(re_url)
        else:
            return render(request, 'rbac/change.html', {'my_form': menu_form})


class EditSec(views.View):
    def get(self, request, smid):
        obj = models.Permission.objects.filter(id=smid).first()
        if not obj:
            return HttpResponse('404 not found')

        p_form = SecMenuModelForm(instance=obj)
        return render(request, 'rbac/change.html', {'my_form': p_form})

    def post(self, request, smid):
        obj = models.Permission.objects.filter(id=smid).first()
        p_form = SecMenuModelForm(instance=obj, data=request.POST)
        if p_form.is_valid():
            p_form.save()
            re_url = reverse('rbac_menu_list')
            params = request.GET.get('_addi')
            if params:
                re_url = '%s?%s' % (re_url, params)
            return redirect(re_url)
        else:
            return render(request, 'rbac/change.html', {'my_form': p_form})


class DelSec(views.View):
    def get(self, request, smid):
        params = request.GET.get('_addi')
        re_url = reverse('rbac_menu_list')
        if params:
            re_url = '%s?%s' % (re_url, params)

        return render(request, 'rbac/delete.html', {'cancel': re_url})

    def post(self, request, smid):
        models.Permission.objects.filter(id=smid).delete()
        params = request.GET.get('_addi')
        re_url = reverse('rbac_menu_list')
        if params:
            re_url = '%s?%s' % (re_url, params)
        return redirect(re_url)


class PerAdd(views.View):
    def get(self, request, sec_menu_id):
        per_form = PerMenuModelForm()
        return render(request, 'rbac/change.html', {'my_form': per_form})

    def post(self, request, sec_menu_id):
        per_form = PerMenuModelForm(data=request.POST)
        p_obj = models.Permission.objects.filter(id=sec_menu_id).first()
        if not p_obj:
            return HttpResponse('404 not found')

        if per_form.is_valid():
            per_form.instance.p = p_obj
            per_form.save()
            re_url = reverse('rbac_menu_list',)
            params = request.GET.get('_addi')
            if params:
                re_url = '%s?%s' % (re_url, params)
            return redirect(re_url)
        else:
            return render(request, 'rbac/change.html', {'my_form': per_form})


class PerEdit(views.View):
    def get(self, request, pid):
        obj = models.Permission.objects.filter(id=pid).first()
        if not obj:
            return HttpResponse('404 not found')

        p_form = PerMenuModelForm(instance=obj)
        return render(request, 'rbac/change.html', {'my_form': p_form})

    def post(self, request, pid):
        obj = models.Permission.objects.filter(id=pid).first()
        p_form = PerMenuModelForm(instance=obj, data=request.POST)
        if p_form.is_valid():
            p_form.save()
            re_url = reverse('rbac_menu_list')
            params = request.GET.get('_addi')
            if params:
                re_url = '%s?%s' % (re_url, params)
            return redirect(re_url)
        else:
            return render(request, 'rbac/change.html', {'my_form': p_form})


class PerDel(views.View):
    def get(self, request, pid):
        params = request.GET.get('_addi')
        re_url = reverse('rbac_menu_list')
        if params:
            re_url = '%s?%s' % (re_url, params)

        return render(request, 'rbac/delete.html', {'cancel': re_url})

    def post(self, request, pid):
        models.Permission.objects.filter(id=pid).delete()
        params = request.GET.get('_addi')
        re_url = reverse('rbac_menu_list')
        if params:
            re_url = '%s?%s' % (re_url, params)
        return redirect(re_url)


def multi_permissions(request):
    all_url_dict = get_all_url_dict()
    all_url_set = set(all_url_dict.keys())
    all_db_permissions = models.Permission.objects.all().values('id', 'title', 'url', 'name', 'menu_id', 'p_id')

    all_db_per_dict = OrderedDict()
    all_db_per_set = set()
    for item in all_db_permissions:
        all_db_per_dict[item.get('name')] = item
        all_db_per_set.add(item.get('name'))

    for k1, v1 in all_db_per_dict.items():
        if k1 in all_url_set:
            if v1['url'] != all_url_dict[k1]['url']:
                v1['url'] = 'Not the same as in the program'

    add_per_set = all_url_set - all_db_per_set
    del_per_set = all_db_per_set - all_url_set
    update_per_set = all_url_set & all_db_per_set

    add_per_formset_class = formset_factory(MultiAddPermissionForm, extra=0)
    add_per_formset = add_per_formset_class(
        initial=[v for k, v in all_url_dict.items() if k in add_per_set]
    )

    edit_per_formset_class = formset_factory(MultiEditPermissionForm, extra=0)
    edit_per_formset = edit_per_formset_class(
        initial=[v for k, v in all_db_per_dict.items() if k in update_per_set]
    )

    del_per_list = [v for k, v in all_db_per_dict.items() if k in del_per_set]

    if request.method == 'POST' and request.GET.get('type') == 'add':
        add_per_formset = add_per_formset_class(data=request.POST)
        if add_per_formset.is_valid():
            c_data_list = add_per_formset.cleaned_data
            has_error = False
            create_list = []
            for i in range(0, add_per_formset.total_form_count()):
                row_data = c_data_list[i]  # 字典
                tmp_obj = models.Permission(**row_data)
                try:
                    tmp_obj.validate_unique()  # 判断是否重复
                    create_list.append(tmp_obj)
                except Exception as e:
                    add_per_formset.errors[i].update(e)
                    has_error = True

            if not has_error:
                models.Permission.objects.bulk_create(create_list, batch_size=50)  # 数据库中批量创建数据
                return redirect('rbac_multi_permissions')

    if request.method == 'POST' and request.GET.get('type') == 'edit':
        edit_per_formset = edit_per_formset_class(data=request.POST)
        if edit_per_formset.is_valid():
            c_data_list = edit_per_formset.cleaned_data
            has_error = False
            for i in range(0, edit_per_formset.total_form_count()):
                row_data = c_data_list[i]  # 字典
                per_id = row_data.pop('id')
                tmp_obj = models.Permission.objects.all().filter(id=per_id).first()
                try:
                    for k, v in row_data.items():
                        setattr(tmp_obj, k, v)
                    tmp_obj.validate_unique()  # 判断是否重复
                    tmp_obj.save()
                except Exception as e:
                    edit_per_formset.errors[i].update(e)
                    has_error = True

            if not has_error:
                return redirect('rbac_multi_permissions')

    return render(request, 'rbac/multi_permissions.html', {
        'add_per_formset': add_per_formset,
        'del_per_list': del_per_list,
        'edit_per_formset': edit_per_formset
    })


class MultiPerDel(views.View):
    def get(self, request, pid):
        re_url = reverse('rbac_multi_permissions')

        return render(request, 'rbac/delete.html', {'cancel': re_url})

    def post(self, request, pid):
        models.Permission.objects.filter(id=pid).delete()

        return redirect('rbac_multi_permissions')


class DistributePermission(views.View):
    def get(self, request):
        user_id = request.GET.get('uid')
        user_obj = models.UserInfo.objects.filter(id=user_id).first()
        if not user_obj:
            user_id = None
        else:
            user_id = int(user_id)

        role_id = request.GET.get('rid')  # 当前选中的角色id
        role_obj = models.Role.objects.filter(id=role_id).first()
        if not role_obj:
            role_id = None
        else:
            role_id = int(role_id)

        user_roles_dict = {}
        all_per_dict = {}
        if role_id:
            role_pers = role_obj.permissions.all()
            all_per_dict = {row.id: None for row in role_pers}  # 当前角色的所有权限
        if user_id:
            all_roles = user_obj.roles.all()
            user_roles_dict = {row.id: None for row in all_roles}  # 当前用户的所有角色
            all_per_list = all_roles.filter(permissions__isnull=False).values('permissions')
            all_per_dict = {item['permissions']: None for item in all_per_list}  # 当前用户的所有权限

        all_user_list = models.UserInfo.objects.all()
        all_role_list = models.Role.objects.all()

        # begin 生成权限分配板块所需数据结构
        first_menu_list = models.Menu.objects.all().values('id', 'title')
        second_menu_list = models.Permission.objects.filter(menu__isnull=False).values('id', 'title', 'menu_id')
        third_menu_list = models.Permission.objects.filter(p__isnull=False).values('id', 'title', 'p_id')

        first_menu_dict = {}
        for item in first_menu_list:
            first_menu_dict[item['id']] = item
            item['children'] = []

        for item in second_menu_list:
            first_menu_dict[item['menu_id']]['children'].append(item)
            item['children'] = []

        second_menu_dict = {}
        for item in second_menu_list:
            second_menu_dict[item['id']] = item

        for item in third_menu_list:
            pid = item['p_id']
            second_menu_dict[pid]['children'].append(item)
        # end

        return render(request, 'rbac/distribute_permission.html', {
            'user_list': all_user_list,
            'role_list': all_role_list,
            'first_menu_list': first_menu_list,
            'user_id': user_id,
            'user_roles_dict': user_roles_dict,
            'all_per_dict': all_per_dict,
            'role_id': role_id
        })

    def post(self, request):
        if request.GET.get('uid'):
            user_id = request.GET.get('uid')
            role_id_list = request.POST.getlist('roles')
            user_obj = models.UserInfo.objects.filter(id=user_id).first()
            user_obj.roles.set(role_id_list)

            re_url = '%s?uid=%s' % (reverse('rbac_distribute_permission'), user_id)
            return redirect(re_url)

        if request.GET.get('rid'):
            role_id = request.GET.get('rid')
            per_id_list = request.POST.getlist('per')
            role_obj = models.Role.objects.filter(id=role_id).first()
            role_obj.permissions.set(per_id_list)

            re_url = '%s?rid=%s' % (reverse('rbac_distribute_permission'), role_id)
            return redirect(re_url)
